/* Start Header
*****************************************************************/
/*!
\file   Client.cpp
\author <Liu Wei Long, weilong.liu, 250002510>
        <Liu Zhi Yang, zhiyang.liu, 300000111>
\par    <email: weilong.liu\@digipen.edu> 
        <email: zhiyang.liu\@digipen.edu> 
\date   <Apr 3, 2012>
\brief
Copyright (C) 2012 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the
prior written consent of DigiPen Institute of Technology is prohibited.
*/
/* End Header
*******************************************************************/
#include "Client.h"
#include "CommonMessages.h"
#include "Timer.h"
#include <deque>

Client::Client(std::string config)
{
  // Read config file
	std::fstream file(config.c_str(), std::fstream::in);
	// Obtain the server address
	file.getline(m_ServerIPAddrStr, 32);
	// Obtain the server port number
	file.getline(m_ServerPortNumberStr, 10);
	// Obtain the UDP port for p2p client
	file.getline(m_UDPPortNumberStr, 10);
	file.close(); 


  // Assign the member variables
  m_serverIP = m_ServerIPAddrStr;
  m_serverPort = atoi(m_ServerPortNumberStr);
  m_UDPport = std::atoi(m_UDPPortNumberStr);
  // Create timer
  m_clientTimer = new Timer;

  m_Terminate = false;   // Boolean to determine when to stop receiving and sending loops
  m_readyToSend = false; // Only sends at the end of every game loop
	// A struct that contains information about Windows Sockets implementation.
	WSADATA wsaData;
	
	// This function initialize the block of memory with zeros 
	SecureZeroMemory( &wsaData, sizeof(wsaData) );
	
	// Initialize the use of Winsock DLL (version 2.2).
	// If successful, it will return zero.
	int i_Result = WSAStartup( MAKEWORD(2,2), &wsaData );
	
	if( i_Result )
	{
		std::cerr << "WSAStartup failed" << std::endl;
		return;
	}
}

Client::~Client(void)
{
  //Close both UDP socket and cleanup WSA
  if (m_UDPClientSocket)
  {
    delete m_UDPClientSocket;
  }
  m_UDPClientSocket = 0;
  // Delete timer
  delete m_clientTimer;

  WSACleanup();
}

bool Client::Setup(void)
{
	int i_Result = 0;
	m_UDPClientSocket = new TSSocket();
  // Set up UDP client socket, error checking done within function call already
	i_Result = m_UDPClientSocket->SetUpUDPClientAddress(m_UDPPortNumberStr);
	if(i_Result == 0)
    return false;

	return true;
}

// Sends server my username and port number
bool Client::SendUserName(void)
{
  std::string str_SendBuffer, str_Username;
	int i_SendResult = 0;

	std::cout << "Your Name < less than 30 characters >: ";
	std::getline(std::cin, str_Username);
	std::cout << std::endl;
  
	str_SendBuffer.append(MessageType::Username);
	str_SendBuffer.append(str_Username);
	str_SendBuffer.append("//");
	str_SendBuffer.append(m_UDPPortNumberStr);
	str_SendBuffer.append("//");

  bool terminateSending = false;
  while(!terminateSending)
  {
	  i_SendResult = m_UDPClientSocket->SendUDPData(str_SendBuffer.c_str(), str_SendBuffer.size(), m_ServerIPAddrStr, m_UDPport);

	  if( i_SendResult == SOCKET_ERROR )
	  {
		  std::cerr << "Error in send(): " << WSAGetLastError() << std::endl;
		  return false;
	  }

    if(WaitForAck(IDLE_TIMER, 0)) // Loops until ack received or time out
    {
      // Ack received
      // Send an ack to the server
      m_UDPClientSocket->SendUDPData(MessageType::TerminateACK.c_str(), MessageType::TerminateACK.length(), m_serverIP, m_serverPort);
      std::cout << "Connection confirmed.\n";
      terminateSending = true;
    }
  }
	return true;
}

bool Client::WaitForAck(float timeOutInterval, unsigned int ackNum)
{
  m_startIdleTimer = m_clientTimer->GetTime();

  /* Receive some acks and free up some memory */
  bool ackreceived = false;
  char bytes[DATA_SIZE * 2];
  int bytesread;
  while (ackreceived = (m_UDPClientSocket->RecvUDPData(bytes, bytesread)))
  {
    if (bytesread == 0) //non blocking call cases
      break;

    m_startIdleTimer = m_clientTimer->GetTime();

    ACKPacket* ackpkt;
    ackpkt = (ACKPacket*)bytes;

    // Ack Received
		if (ackNum == ackpkt->mAckedNumber)
		{
			return true; // Return true to indicate ack has been received
		}
    
    //check for idle timeout
    if (m_clientTimer->GetTimeDiff(m_startIdleTimer, m_clientTimer->GetTime()) > IDLE_TIMER)
    {
      std::cout << "Idle timer reached. No response from server, stopping connection..\n";
      break;
    }
  }
  Sleep(50);

  return 0;
}

bool Client::Run(void)
{
	CreateReceivingThread();// Thread in charge of Receiving data to the server
	std::string data("test");
	int i_Result = SOCKET_ERROR;

	while(!Client::m_Terminate)
	{
    if(m_readyToSend)
    {
      i_Result = m_UDPClientSocket->SendUDPData(data.c_str(), data.size(), m_serverIP, m_serverPort);
    }
		if(i_Result == 0)
		{
			std::cout << "Server crashed or disconnected..." << std::endl;
			std::cout << "Client is exiting from the game now" << std::endl;

			m_Terminate = true;
			WSACleanup();	

			return false;
		}
		else if(i_Result == SOCKET_ERROR)
		{
			int errCode = WSAGetLastError();
			if(errCode == WSAEWOULDBLOCK)
			{
				Sleep(100);
				continue;
			}
			else
			{
				m_Terminate = true;
			}
		}

		data.clear();
	}

	return false;
}


void Client::CreateReceivingThread(void)
{
	  mReceivingThreadHandle = CreateThread(
		NULL,							// default security descriptor
		0,								// initial size of the stack, in bytes. If zero, the thread uses default size for the exe
		&Client::ReceiveUDPDataLoop, 	// pointer to the starting address of the thread
		(void*)this, 					// pointer to a variable to be passed to the thread
		0, 								// the flags that control the creation of the thread
		NULL); 							// thread id
}


DWORD WINAPI Client::ReceiveUDPDataLoop(void* args)
{
  Client* client = (Client*)args;
  std::string serverIP(client->m_ServerIPAddrStr);
  std::string portString(client->m_ServerPortNumberStr);
  int servPortNum = std::atoi(portString.c_str());

  // Create timer for this packet
  Timer *m_packetTimer= new Timer;
  LARGE_INTEGER startidletime = m_packetTimer->GetTime();

  while(!client->m_Terminate)
  {
    /* Receive some acks and free up some memory */
    bool ackreceived = false;
    char bytes[DATA_SIZE * 2];
    int bytesread;
    ackreceived = (client->m_UDPClientSocket->RecvUDPData(bytes, bytesread));

    //check for idle timeout
    if (m_packetTimer->GetTimeDiff(startidletime, m_packetTimer->GetTime()) > IDLE_TIMER)
    {
      std::cout << "Idle timer reached. No response from downloader, stopping connection..\n";
      break;
    }
  }
  return 0;
}

void Client::MessageParser(const char* dataFromServer)
{
  return;
}
//void Client::MessageParser(const char* dataFromServer)
//{
//  std::string message(dataFromServer);
//  std::string seederIP;
//  if(message.find(MessageType::Seeder) != std::string::npos) // If seeder text can be found, create a new seeder
//  {
//    message.erase(0, MessageType::Seeder.size());
//    size_t pos = message.find("//");
//    seederIP = message.substr(0, pos); //get ip addr
//
//    message.erase(0, pos + 2);
//    pos = message.find("//");
//    std::string seederport = message.substr(0, pos);
//
//    message.erase(0, pos + 2);
//    pos = message.find("//");
//    std::string filename = message.substr(0, pos);
//    // Create new seeder with the following properties
//    isSending = true;
//    ClientSeeder* newSeeder = new ClientSeeder(this, seederIP, atoi(seederport.c_str()), mUDPClientSocket, filename);
//    // Create a new thread that calls the seeder loop and takes the new seeder as an argument
//    HANDLE threadhandle = CreateThread(NULL, 0, &ClientSeeder::SeederLoop, (LPVOID) newSeeder, 0, NULL);
//    mFileTransferThreadHandles.push_back(threadhandle);
//  }
//  else if(message.find(MessageType::Downloader) != std::string::npos) // Create downloader
//  {
//    message.erase(0, MessageType::Downloader.size());
//    size_t pos = message.find("//");
//    seederIP = message.substr(0, pos); //get ip addr
//
//    message.erase(0, pos + 2);
//    pos = message.find("//");
//    std::string seederport = message.substr(0, pos);
//
//    message.erase(0, pos + 2);
//    pos = message.find("//");
//    std::string filename = message.substr(0, pos);
//    // Create new seeder with the following properties
//    isSending = true;
//    ClientDownloader* newDownloader = new ClientDownloader(this, seederIP, atoi(seederport.c_str()), mUDPClientSocket, filename);
//    // Create a new thread that calls the downloader loop and takes the new downloader as an argument
//    HANDLE threadhandle = CreateThread(NULL, 0, &ClientDownloader::DownloaderLoop, (LPVOID) newDownloader, 0, NULL);
//    mFileTransferThreadHandles.push_back(threadhandle);
//  }
//  else if(message.find(MessageType::Exit) != std::string::npos) // Exit
//  {
//    m_Terminate = true;
//  }
//  std::cout << dataFromServer << std::endl;
//}
